


/**深拷贝。支持循环引用、map、set、Date、RegExp、Function。
 * - 缺点：function无法保留作用域，不支持Symbol深拷贝 
 * 
 * - 默写思路：
 * 1. 先判断是不是对象（不用深拷贝的数据），是的话就返回原数据 （注意区分null）
 * 2. 判断是否命中缓存
 * 3. 处理特殊数据
 *   a. 日期、正则就调用对应构造函数返回新数据
 *   b. （若有需求） 返回toString的新函数
 *   c. 处理Map和Set 
 * 4. 遍历原对象，递归拷贝新内容（注意缓存时机、利用Reflect和Object的一些方法）
 */
export default function deepClone<T>(target: T): T {
    /**防止循环引用的map */
    const cache = new WeakMap()

    function clone(data: any) {

        // 基础类型直接返回值
        if (!((typeof data === 'object' && data) || typeof data === 'function')) {
            return data
        }

        // 如果该对象已存在，则直接返回该对象 
        if (cache.has(data)) {
            return cache.get(data)
        }

        // 日期或者正则对象则直接构造一个新的对象返回
        if ([Date, RegExp].includes(data.constructor)) {
            return new data.constructor(data)
        }

        // 处理函数对象
        if (typeof data === 'function') {
            return new Function('return ' + data.toString())() //这里需要调用，是因为 data.toString() 是个完整的函数字符串，相当于new Function是一个高阶函数了
        }


        // 处理Map对象
        if (data instanceof Map) {
            const result = new Map()
            cache.set(data, result)
            data.forEach((val, key) => {
                result.set(key, clone(val))
            })
            return result
        }

        // 处理Set对象
        if (data instanceof Set) {
            const result = new Set()
            cache.set(data, result)
            data.forEach(val => {
                result.add(clone(val))
            })
            return result
        }

        const keys = Reflect.ownKeys(data)// 收集键名（考虑了以Symbol作为key以及不可枚举的属性） 
        const allDesc = Object.getOwnPropertyDescriptors(data)// 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述(比如是否可枚等)
        const result = Object.create(Object.getPrototypeOf(data), allDesc)//继承data的原型，并且浅拷贝allDesc(具有相同的属性描述符)


        //如果不记得那么多怎么写了，最起码用个 const keys = Reflect.ownKeys(data) ， 然后 const result = Array.isArray(data) ? [] : {}， 最后遍历


        // 新对象加入到map中，进行记录 （需要先缓存，再进行下面的操作）
        cache.set(data, result)

        // Object.create()是浅拷贝，所以要判断并递归执行深拷贝
        keys.forEach(key => {
            result[key] = clone(data[key])
        })

        return result
    }

    return clone(target)
}


const symbolB = Symbol('b')
type CirculateObj = {
    out: string,
    /**循环1 */
    obj1: Obj1
}
/**循环1 */
type Obj1 = {
    a: Map<number, any>,
    b: Set<number>,
    /**循环2 */
    obj2: Obj2
}
/**循环2 */
type Obj2 = {
    a: number,
    [symbolB]: string,
    /**循环3 */
    obj3: Obj3
}
/**循环3 */
type Obj3 = {
    a: number,
    b: {
        /**循环1 */
        obj1: Obj1
    }
}
const obj3: Obj3 = {
    a: 3,
    b: {
        obj1: "obj1" as unknown as typeof obj1,//这里最终要放入obj1
    },
};
const obj2: Obj2 = {
    a: 2,
    [symbolB]: "2b",
    obj3: obj3,
};
const obj1: Obj1 = {
    a: new Map<number, any>([[1, 2], [2, { a: 1 }]]),
    b: new Set([1, 2, 3]),
    // get c() {
    //     return "1c";
    // },
    obj2: obj2,
};
const obj: CirculateObj = {
    out: "out",
    obj1: "obj1" as unknown as typeof obj1,//这里最终要放入obj1
};
obj3.b.obj1 = obj1;
obj.obj1 = obj1;

/**循环引用的对象 */
export const circulateObj = obj

// const newObj = deepClone(circulateObj)
// console.log('原', circulateObj);
// console.log('新', newObj);
// console.log('是否相同', newObj === circulateObj);

// console.log('修改——————————');
// circulateObj.obj1.obj2.obj3.a = 666
// circulateObj.obj1.b.delete(1)
// circulateObj.obj1.a.set(2, 2)
// console.log('原', circulateObj);
// console.log('新', newObj);
// console.log('是否相同', newObj === circulateObj);



